
#include <raylib.h>

#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <map>
#include <memory>
#include <utility>
#include <vector>

using namespace std;

#define EMPTY 0
#define MINE -1

class Block {
 public:
  Block() {
    mRevealed = false;
    mMarked = false;
    mType = 0;
  }

  Block(int btype) {
    mRevealed = false;
    mMarked = false;
    mType = btype;
  }

  void setRevealed(bool reveal) { mRevealed = reveal; }

  void setMarked(bool mark) { mMarked = mark; }

  void setType(int btype) { mType = btype; }

  bool isRevealed() { return mRevealed; }

  bool isMarked() { return mMarked; }

  int getType() { return mType; }

 private:
  bool mRevealed;
  bool mMarked;
  int mType;  // Ranges from -1 to 8
};

class SweepMineGame {
 public:
  SweepMineGame(int level = 0) {
    if (level == 0) {
      cols = rows = 9;
    } else if (level == 1) {
      cols = rows = 13;
    } else if (level == 2) {
      cols = rows = 18;
    }

    cells.clear();
    // 上下左右都插入一排保留的格子，使得计算邻居时更加统一
    for (int i = 0; i < rows + 2; i++) {
      vector<Block> cell_row;
      for (int j = 0; j < cols + 2; j++) {
        cell_row.emplace_back();
      }

      cells.push_back(cell_row);
    }

    restartGame();

    InitWindow(cols * LENGTH, rows * LENGTH, "Mine Sweep");
    SetTargetFPS(60);
  }
  ~SweepMineGame() { CloseWindow(); }

  void run() {
    // Main game loop
    while (!WindowShouldClose()) {  // Detect window close button or ESC key

      process_mouse_event2();

      // Draw
      BeginDrawing();
      update_frame2();
      EndDrawing();
    }
  }

 private:
  // 处理鼠标事件
  void process_mouse_event2() {
    Vector2 mouse = GetMousePosition();

    // 鼠标左键点击
    //----------------------------------------------------------------------------------
    if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
      if (!gameover && !gamewon) {
        int y = floor(mouse.y / 50) + 1;
        int x = floor(mouse.x / 50) + 1;
        if (y >= 1 && y <= rows && x >= 1 && x <= cols) {
          if (cells[y][x].isMarked() == false) {
            cells[y][x].setRevealed(true);
            if (cells[y][x].getType() == EMPTY) {  // CHECK IF 'EMPTY'
              cells[y][x].setRevealed(false);
              revealBlocks(y, x);
            }

            if (cells[y][x].getType() == MINE) {
              gameover = true;
            }
          }
        }
      }
    }

    // 鼠标右键点击
    //----------------------------------------------------------------------------------
    if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) {
      if (!gameover && !gamewon) {
        int y = floor(mouse.y / 50) + 1;
        int x = floor(mouse.x / 50) + 1;
        if (y >= 1 && y <= rows && x >= 1 && x <= cols) {
          if (cells[y][x].isRevealed() == false) {
            if (cells[y][x].isMarked() == false) {
              cells[y][x].setMarked(true);
            } else {
              cells[y][x].setMarked(false);
            }
          }
        }
      }
    }
  }

  void update_frame2() {
    // Draw the blocks
    //--------------------------------------------------------------------------------------
    for (int i = 0; i < rows; ++i) {
      for (int j = 0; j < cols; ++j) {
        if (cells[i + 1][j + 1].isRevealed() == false) {
          DrawRectangle(j * 50, i * 50, 50, 50, LIGHTGRAY);
          DrawRectangleLines(j * 50, i * 50, 50, 50, BLACK);
        } else {
          if (cells[i + 1][j + 1].getType() == MINE) {
            DrawRectangle(j * 50, i * 50, 50, 50, RED);
            DrawRectangleLines(j * 50, i * 50, 50, 50, BLACK);
          } else if (cells[i + 1][j + 1].getType() == EMPTY) {
            DrawRectangle(j * 50, i * 50, 50, 50, GRAY);
            DrawRectangleLines(j * 50, i * 50, 50, 50, BLACK);
          } else {
            int cell_type = cells[i + 1][j + 1].getType();
            Color c;
            if (color_map.count(cell_type)) {
              c = color_map[cell_type];
            } else {
              c = color_map[5];
            }

            DrawRectangle(j * 50, i * 50, 50, 50, DARKGRAY);
            DrawRectangleLines(j * 50, i * 50, 50, 50, BLACK);
            DrawText(TextFormat("%i", cells[i + 1][j + 1].getType()),
                     j * 50 + 20, i * 50 + 15, 25, c);
          }
        }
      }
    }

    // Draw the marked blocks (flags)
    //----------------------------------------------------------------------------------
    for (int i = 0; i < rows; ++i) {
      for (int j = 0; j < cols; ++j) {
        if (cells[i + 1][j + 1].isMarked() &&
            !cells[i + 1][j + 1].isRevealed()) {
          DrawRectangle(j * 50 + 15, i * 50 + 10, 10, 10, RED);
          DrawRectangle(j * 50 + 25, i * 50 + 10, 5, 25, BLACK);
          DrawRectangle(j * 50 + 20, i * 50 + 35, 15, 5, BLACK);
        }
      }
    }

    // Update variable to determine if won
    //----------------------------------------------------------------------------------
    int totalblocks = 0;
    for (int i = 1; i <= rows; ++i) {
      for (int j = 1; j <= cols; ++j) {
        if (cells[i][j].getType() >= 1 && cells[i][j].isRevealed() == false) {
          totalblocks++;
        }
      }
    }

    // Check for win
    //----------------------------------------------------------------------------------
    if (totalblocks == 0) {
      DrawText("Game won!", 103, 203, 50, BLACK);
      DrawText("Game won!", 100, 200, 50, GREEN);
      DrawText("right click to restart", 101, 251, 25, WHITE);
      DrawText("right click to restart", 100, 250, 25, BLACK);
      gamewon = true;

      for (int i = 1; i <= rows; ++i) {
        for (int j = 1; j <= cols; ++j) {
          cells[i][j].setRevealed(true);
        }
      }

      if ((IsMouseButtonReleased(MOUSE_RIGHT_BUTTON))) {
        restartGame();
        gamewon = false;
      }
    }

    // Check for gameover
    //----------------------------------------------------------------------------------
    if (gameover == true) {
      DrawText("Game Over", 103, 203, 50, BLACK);
      DrawText("Game Over", 100, 200, 50, RED);
      DrawText("right click to restart", 101, 251, 25, WHITE);
      DrawText("right click to restart", 100, 250, 25, BLACK);

      for (int i = 1; i <= rows; ++i) {
        for (int j = 1; j <= cols; ++j) {
          cells[i][j].setRevealed(true);
        }
      }

      if ((IsMouseButtonReleased(MOUSE_RIGHT_BUTTON))) {
        restartGame();
        gameover = false;
      }
    }
  }

  // Restart the game
  void restartGame() {
    // Generate mines
    srand(time(NULL));

    // 随机生成 Mines
    for (int i = 1; i <= rows; ++i) {
      for (int j = 1; j <= cols; ++j) {
        cells[i][j].setType((randomnum() == 1) ? MINE : EMPTY);
      }
    }
    for (int i = 1; i <= rows; ++i) {
      for (int j = 1; j <= cols; ++j) {
        if (cells[i][j].getType() != MINE) {
          countMines(i, j);
        }
      }
    }
  }

  void countMines(int x, int y) {
    int count = 0;
    for (int i = x - 1; i <= x + 1; i++) {
      for (int j = y - 1; j <= y + 1; j++) {
        if (i == x && j == y)
          continue;
        else if (cells[i][j].getType() == MINE) {
          count++;
        }
      }
    }
    if (count != 0) cells[x][y].setType(count);
  }
  // Floodfill algorithm
  void revealBlocks(int x, int y) {
    if (x >= 1 && x <= rows && y >= 1 && y <= cols) {
      if (cells[x][y].getType() != EMPTY) {
        cells[x][y].setRevealed(true);
      } else {
        if (cells[x][y].isRevealed() == false) {
          cells[x][y].setRevealed(true);
          revealBlocks(x - 1, y);
          revealBlocks(x - 1, y - 1);
          revealBlocks(x - 1, y + 1);

          revealBlocks(x, y - 1);
          revealBlocks(x, y + 1);

          revealBlocks(x + 1, y - 1);
          revealBlocks(x + 1, y);
          revealBlocks(x + 1, y + 1);
        }
      }
    }
  }

  // Generate random number
  inline int randomnum() { return rand() % (int(sqrt(cols * rows)) + 1); }

 private:
  bool gameover = false;
  bool gamewon = false;
  const int LENGTH = 50;
  int level = 0;  // 0,1,2
  int cols, rows;

  map<int, Color> color_map = {
      {1, SKYBLUE}, {2, GREEN}, {3, RED}, {4, DARKBLUE}, {5, MAROON},
  };
  vector<vector<Block>> cells;
};

int main() {
  SweepMineGame game;
  game.run();
  return 0;
}
